/*
 * Copyright 2011-2021 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0.
 * See `LICENSE` in the project root for license information.
 */

package me.ijleex.mgmt.system.auth.service.impl;

import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.List;
import java.util.stream.Collectors;

import org.jose4j.json.JsonUtil;
import org.jose4j.jwk.JsonWebKey;
import org.jose4j.jwk.RsaJsonWebKey;
import org.jose4j.jwk.RsaJwkGenerator;
import org.jose4j.jws.JsonWebSignature;
import org.jose4j.jwt.JwtClaims;
import org.jose4j.jwt.NumericDate;
import org.jose4j.jwt.consumer.InvalidJwtException;
import org.jose4j.jwt.consumer.JwtConsumer;
import org.jose4j.jwt.consumer.JwtConsumerBuilder;
import org.jose4j.lang.JoseException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import me.ijleex.mgmt.commons.util.snowflake.IdWorker;
import me.ijleex.mgmt.framework.entity.Result;
import me.ijleex.mgmt.framework.json.util.JacksonUtils;
import me.ijleex.mgmt.system.auth.autoconfigure.AuthProperties;
import me.ijleex.mgmt.system.auth.model.JwtUser;
import me.ijleex.mgmt.system.auth.service.IJwtService;
import me.ijleex.mgmt.system.role.entity.Role;

/**
 * JwtService.
 *
 * @author liym
 * @since 2020-07-18 22:08 新建
 */
@Service
public class JwtServiceImpl implements IJwtService {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    private final int keySize;
    private final String keyUse;
    /**
     * 在开发应用的时候启用 JWT，使用 RS256、ES256 更加安全
     */
    private final String algorithm;
    private final String keyId;

    private final String publicKey;
    private final String privateKey;

    private final long expireTime;

    public JwtServiceImpl(AuthProperties properties) {
        AuthProperties.Jwt jwt = properties.getJwt();
        this.keySize = jwt.getSize();
        this.keyUse = jwt.getUse();
        this.algorithm = jwt.getAlgorithm();
        this.keyId = jwt.getKeyId();
        this.publicKey = jwt.readPublicKey();
        this.privateKey = jwt.readPrivateKey();
        this.expireTime = jwt.getExpireTime().getSeconds();
    }

    @Override
    public Result createKeyPair() {
        JsonWebKey jwk;
        try {
            jwk = RsaJwkGenerator.generateJwk(this.keySize);
        } catch (JoseException e) {
            this.logger.error("createKeyPair error", e);
            return Result.error("createKeyPair");
        }

        jwk.setUse(this.keyUse);
        jwk.setKeyId(this.keyId);
        jwk.setAlgorithm(this.algorithm);

        String publicKey = jwk.toJson(RsaJsonWebKey.OutputControlLevel.PUBLIC_ONLY);
        String privateKey = jwk.toJson(RsaJsonWebKey.OutputControlLevel.INCLUDE_PRIVATE);

        return Result.ok("已成功").addData("keyId", this.keyId).addData("publicKey", publicKey).addData("privateKey", privateKey);
    }

    @Override
    public Result createToken(final long userId, final String username, String password, List<Role> roles) {
        // 过期时间一定要设置，并且小于7天
        NumericDate date = NumericDate.now();
        date.addSeconds(this.expireTime);

        // ===

        JwtClaims claims = new JwtClaims();
        // JWT签发者（签名是由谁生成）
        claims.setIssuer(Long.toString(userId));
        // 主题（JWT面向的用户）
        claims.setSubject(username);
        // 签名的观众（谁接受签名）
        claims.setAudience("APP");
        // 签名过期的时间
        claims.setExpirationTime(date);
        // 在此之前，令牌无效（2分钟前）
        claims.setNotBeforeMinutesInThePast(2);
        // 签发时间（现在）
        claims.setIssuedAtToNow();
        // 令牌的唯一标识，主要用来作为一次性Token，从而回避重放攻击
        long keyId = IdWorker.getId();
        claims.setJwtId(Long.toString(keyId));

        // 添加自定义参数，所有值请都使用String类型
        claims.setClaim("password", password);
        claims.setClaim("roles", roles.stream().map(Role::getAuthority).collect(Collectors.toList()));
        claims.setClaim("enabled", true);

        // JWT是一个JWS和/或一个带有JSON声明的JWE作为有效负载。
        JsonWebSignature jws = new JsonWebSignature();
        jws.setPayload(claims.toJson());
        jws.setAlgorithmHeaderValue(this.algorithm);
        jws.setKeyIdHeaderValue(this.keyId);
        try {
            PrivateKey key = new RsaJsonWebKey(JsonUtil.parseJson(this.privateKey)).getPrivateKey();
            jws.setKey(key);

            String idToken = jws.getCompactSerialization();
            return Result.ok(idToken);
        } catch (JoseException e) {
            this.logger.error("createToken error", e);
            return Result.error("createToken");
        }
    }

    @Override
    public Result validateToken(final String token) {
        PublicKey publicKey;
        try {
            publicKey = new RsaJsonWebKey(JsonUtil.parseJson(this.publicKey)).getPublicKey();
        } catch (JoseException e) {
            this.logger.error("validateToken error", e);
            return Result.error("validateToken");
        }

        JwtConsumer consumer = new JwtConsumerBuilder()
                .setVerificationKey(publicKey)
                .setExpectedAudience("APP")
                // .setExpectedIssuer("")
                .setRequireSubject()
                .setRequireExpirationTime()
                // 允许在验证基于时间的令牌时留有一定的余地，以计算时钟偏差。
                .setAllowedClockSkewInSeconds(30)
                .setMaxFutureValidityInMinutes(525600)
                .build();
        try {
            JwtClaims claims = consumer.processToClaims(token);
            String json = claims.getRawJson();

            JwtUser jwtUser = JacksonUtils.jsonToObj(json, JwtUser.class);
            return Result.ok(jwtUser);
        } catch (InvalidJwtException e) {
            if (e.hasExpired()) {
                return Result.error("Token 已过期。");
            }
            return Result.error("Token 无效");
        }
    }

}
